package com.young.testcoroutine

import kotlinx.coroutines.*
import org.junit.Test
import java.io.IOException

/**
 * Author：young
 * .
 * Time： 2022/9/14 22:34 周三
 * .
 * Desc：异常捕获
 */
class CoroutineTest08 {

    /**
     * 测试 CoroutineExceptionHandler 捕获异常
     */
    @Test
    fun `test CoroutineExceptionHandler`() = runBlocking<Unit> {

        val handler = CoroutineExceptionHandler { _, throwable ->
            println("异常被我抓住了 $throwable")
        }

        // 这个异常会被捕获
        val job = GlobalScope.launch(handler) {
            throw AssertionError()
        }

        // 这个异常不会被捕获，要在 .await() 加 try catch 才可以
        val deferred = GlobalScope.async(handler) {
            throw IllegalArgumentException()
        }

        job.join()
        deferred.await()

    }

    /**
     * 测试[一个根协程(CoroutineScope 或者supervisorScope 的直接子协程)中] 抛出的异常进行捕获
     *
     * 根协程的直接子协程是 launch/async 抛出的异常都可以被捕获
     */
    @Test
    fun `test CoroutineExceptionHandler2`() = runBlocking<Unit> {
        val handler = CoroutineExceptionHandler { _, throwable ->
            println("异常被我抓住了 $throwable")
        }

        val scope = CoroutineScope(Job())
        val job = scope.launch(handler) {
            // scope.launch(handler) 这个是根协程
            launch {
                // 这个可以被捕获
                // 因为上下文是从父协程继承而来，是同一个
                // 使用 launch 抛出
                throw IllegalArgumentException()
            }

//            async {
//                // 这个可以被捕获
//                // 因为上下文是从父协程继承而来，是同一个
//                // 使用 launch 抛出
//                throw IllegalArgumentException()
//            }
        }


        job.join()

    }

    /**
     * 如果 CoroutineExceptionHandler 安装到内部协程，是无法捕获的，
     * 根据异常的传播特性（向上抛），所以 CoroutineExceptionHandler 必须安装上层协程
     */
    @Test
    fun `test CoroutineExceptionHandler3`() = runBlocking<Unit> {
        val handler = CoroutineExceptionHandler { _, throwable ->
            println("异常被我抓住了 $throwable")
        }

        val scope = CoroutineScope(Job())
        val job = scope.launch {
            // scope.launch(handler) 这个是根协程
            launch(handler) {
                // 这个可以被捕获
                // 因为上下文是从父协程继承而来，是同一个
                // 使用 launch 抛出
                throw IllegalArgumentException()
            }

//            async {
//                // 这个可以被捕获
//                // 因为上下文是从父协程继承而来，是同一个
//                // 使用 launch 抛出
//                throw IllegalArgumentException()
//            }
        }


        job.join()
    }


    /**
     * 测试取消与异常
     */
    @Test
    fun `test cancel and exception`() = runBlocking<Unit> {
        val job = launch {
            val child = launch {
                try {
                    try {
                        delay(Long.MAX_VALUE)
                    } catch (e: Exception) {
                        // 可以通过 catch 来捕获那个取消异常 JobCancellationException
                        // 如果不用 catch 捕获，就会被协程静默处理掉
//                        e.printStackTrace()
                        println("取消异常 $e")
                    }
                } finally {
                    println("子协程被取消了")
                }
            }
            yield()
            println("取消子协程")
            child.cancelAndJoin()
            yield()
            println("父协程没有被取消")
        }
        job.join()
    }


    /**
     * 测试如果一个协程遇到了CancellationException以外的异常，它将使用该异常取消它的父协程。
     * 当父协程的所有子协程都结束后，异常才会被父协程处理
     *
     * 同时测试异常发生后，必要资源的释放
     */
    @Test
    fun `test cancel and exception2`() = runBlocking<Unit> {
        val handler = CoroutineExceptionHandler { _, throwable ->
            println("捕获 $throwable")
        }

        val job = GlobalScope.launch(handler) {
            launch {
                try {
                    delay(Long.MAX_VALUE)
                } finally {
                    // 使用 NonCancellable 来保证下面协程一定可以执行完毕
                    withContext(NonCancellable) {
                        println("开始资源释放了")
                        delay(100)
                        println("资源释放了")
                    }
                }
            }


            launch {
                delay(10)
                println("准备抛出异常")
                throw IllegalArgumentException("我是抛出的异常")
            }

        }

        job.join()

    }


    /**
     * 测试异常聚合
     */
    @Test
    fun `test cancel and aggregation`() = runBlocking<Unit> {
        val handler = CoroutineExceptionHandler { _, throwable ->
            println("捕获 $throwable ${throwable.suppressed.contentToString()}")
            // 获取其他异常
            val suppressedExceptions = throwable.suppressedExceptions
            for (suppressedException in suppressedExceptions) {
                println("${suppressedException.message}")
            }
        }

        val job = GlobalScope.launch(handler) {
            launch {
                try {
                    delay(Long.MAX_VALUE)
                } finally {
                    throw ArithmeticException("子协程 1 的异常")
                }
            }

            launch {
                delay(1000)
                throw IOException("子协程 2 的异常")
            }

        }

        job.join()

    }


}